June 22, 2024
React에서 useRef를 사용하는 방법, 그 특징, 그리고 React에서 useRef의 동작 원리에 대해 알아보겠습니다.
import { useRef } from 'react';
export default function Counter() {
let ref = useRef(0); // useRef 훅으로 ref를 만든다. 인자로는 initialValue가 들어간다.
function handleClick() {
ref.current = ref.current + 1; // ref.current로 해당 값을 사용한다.
alert('You clicked ' + ref.current + ' times!');
}
return <button onClick={handleClick}>Click me!</button>;
}
{ current: value }
로 리턴한다.import { useState, useRef } from 'react';
export default function Stopwatch() {
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);
const intervalRef = useRef(null);
function handleStart() {
setStartTime(Date.now());
setNow(Date.now());
clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
setNow(Date.now());
}, 10);
}
function handleStop() {
clearInterval(intervalRef.current);
}
let secondsPassed = 0;
if (startTime != null && now != null) {
secondsPassed = (now - startTime) / 1000;
}
return (
<>
<h1>Time passed: {secondsPassed.toFixed(3)}</h1>
<button onClick={handleStart}>Start</button>
<button onClick={handleStop}>Stop</button>
</>
);
}
// Inside of React
function useRef(initialValue) {
const [ref, unused] = useState({ current: initialValue });
return ref;
}
다음은 Button을 클릭했을 때 Text input에 focus하는 코드이다.
<input ref={inputRef}>
로 넣어준다. 이렇게 하면 “리액트야~ <input>의 DOM node를 inputRef에 넣어줘”라고 요청하는 것이다.import { useRef } from 'react';
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={handleClick}>Focus the input</button>
</>
);
}
아래와 같이 list에서 표출하는 item의 개수만큼 ref를 만들고 싶을 때가 있다.
<ul>
{items.map(item => {
// Doesn't work!
const ref = useRef(null);
return <li ref={ref} />;
})}
</ul>
이럴 때 ref callback을 사용한다.
사용법은 아래와 같다.
else {}
이 부분은 메모리 누수를 막기 위해 컴포넌트가 unmount될 시 동작한다.<ul>
{catList.map(cat => (
<li
key={cat}
ref={node => {
const map = getMap();
if (node) {
map.set(cat, node);
} else {
map.delete(cat);
}
}}
>
<img src={cat} />
</li>
))}
</ul>
import { useRef, useState } from 'react';
export default function CatFriends() {
const itemsRef = useRef(null);
const [catList, setCatList] = useState(setupCatList);
function scrollToCat(cat) {
const map = getMap();
const node = map.get(cat);
node.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center',
});
}
function getMap() {
if (!itemsRef.current) {
// Initialize the Map on first usage.
itemsRef.current = new Map();
}
return itemsRef.current;
}
return (
<>
<nav>
<button onClick={() => scrollToCat(catList[0])}>Tom</button>
<button onClick={() => scrollToCat(catList[5])}>Maru</button>
<button onClick={() => scrollToCat(catList[9])}>Jellylorum</button>
</nav>
<div>
<ul>
{catList.map(cat => (
<li
key={cat}
ref={node => {
const map = getMap();
if (node) {
map.set(cat, node);
} else {
map.delete(cat);
}
}}
>
<img src={cat} />
</li>
))}
</ul>
</div>
</>
);
}
function setupCatList() {
const catList = [];
for (let i = 0; i < 10; i++) {
catList.push('https://loremflickr.com/320/240/cat?lock=' + i);
}
return catList;
}
const MyInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});
위 예시에서 MyInput은 DOM 입력 요소를 노출한다. 이렇게 하면 부모 컴포넌트가 이 요소에 focus()를 호출할 수 있다. 하지만 이렇게 하면 부모 컴포넌트가 다른 작업(예: CSS 스타일 변경)을 할 수도 있다. 드문 경우지만 노출되는 기능을 제한하고 싶을 수도 있다. 이 경우 useImperativeHandle을 사용한다.
import { forwardRef, useRef, useImperativeHandle } from 'react';
const MyInput = forwardRef((props, ref) => {
const realInputRef = useRef(null);
useImperativeHandle(ref, () => ({
// Only expose focus and nothing else
focus() {
realInputRef.current.focus();
},
}));
return <input {...props} ref={realInputRef} />;
});
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<MyInput ref={inputRef} />
<button onClick={handleClick}>Focus the input</button>
</>
);
}
React에서는 모든 업데이트는 Render, Commit 두 단계로 나눌 수 있다. Render 시에는 DOM이 아직 만들어지기 전이고, 그러므로 ref.current가 null이다. Commit 할 때 ref.current를 첨부한다.